Skip to main content

Databases

Two Main Approaches in Rust

ApproachExample LibraryDescription
ORM (Object Relational Mapper)DieselMaps database tables → Rust structs
Query-based (SQL-first)SQLxYou write raw SQL but get compile-time safety

Diesel — Strongly Typed ORM

Diesel is a type-safe ORM for Rust that:

  • Uses Rust structs to represent database tables.
  • Prevents SQL errors at compile time.
  • Uses migrations and schema generation.
  • Works with PostgreSQL, MySQL, and SQLite.

Step 1: Dependencies (Cargo.toml)

[dependencies]
diesel = { version = "2.1.0", features = ["postgres", "r2d2"] }
dotenvy = "0.15"
serde = { version = "1", features = ["derive"] }

Step 2: Database Setup (PostgreSQL)

Create a .env file:

DATABASE_URL=postgres://user:password@localhost/mydb

Initialize Diesel:

diesel setup
diesel migration generate create_users

Edit the migration files:

Up migration (up.sql)

CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);

Down migration (down.sql)

DROP TABLE users;

Run:

diesel migration run

Step 3: Diesel Schema (schema.rs)

Diesel auto-generates:

diesel::table! {
users (id) {
id -> Int4,
name -> Text,
email -> Text,
}
}

Step 4: Rust Models (models.rs)

use diesel::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Queryable, Serialize)]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
}

#[derive(Insertable, Deserialize)]
#[diesel(table_name = crate::schema::users)]
pub struct NewUser {
pub name: String,
pub email: String,
}

Step 5: Database Operations (main.rs)

use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenvy::dotenv;
use std::env;

mod schema;
mod models;

use models::{User, NewUser};
use schema::users::dsl::*;

fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url).expect("Error connecting to database")
}

// CREATE
fn create_user(conn: &mut PgConnection, new_user: NewUser) -> User {
diesel::insert_into(users)
.values(&new_user)
.get_result(conn)
.expect("Error saving new user")
}

// READ
fn get_users(conn: &mut PgConnection) -> Vec<User> {
users.load::<User>(conn).expect("Error loading users")
}

fn main() {
let conn = &mut establish_connection();

let user = create_user(conn, NewUser {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
});

println!("Created user: {:?}", user);

let all_users = get_users(conn);
println!("All users: {:?}", all_users);
}
  • Queryable: Maps DB rows → Rust structs.
  • Insertable: Maps Rust struct → DB row.
  • schema.rs: Ensures compile-time SQL safety.
  • `Diesel prevents invalid queries at compile time.

SQLx — Async, SQL-First Library

SQLx is an async, non-blocking, SQL-first database library that:

  • Uses raw SQL queries.
  • Validates SQL at compile time.
  • Supports PostgreSQL, MySQL, SQLite.
  • Works perfectly with async web frameworks (Axum, Actix, Rocket).

Step 1: Dependencies (Cargo.toml)

[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "macros"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
dotenvy = "0.15"

Step 2: Database Setup

Same database table:

CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
);

Step 3: Rust Code (main.rs)

use serde::{Deserialize, Serialize};
use sqlx::{PgPool, postgres::PgPoolOptions};
use dotenvy::dotenv;
use std::env;

#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct User {
id: i32,
name: String,
email: String,
}

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");

let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;

// CREATE
let new_user = User {
id: 0,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
};

let created: User = sqlx::query_as!(
User,
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
new_user.name,
new_user.email
)
.fetch_one(&pool)
.await?;

println!("Created user: {:?}", created);

// READ
let users: Vec<User> = sqlx::query_as!(
User,
"SELECT id, name, email FROM users"
)
.fetch_all(&pool)
.await?;

println!("All users: {:?}", users);

Ok(())
}
  • query_as!: Checks SQL at compile time.
  • $1, $2: Parameter placeholders.
  • fetch_one, fetch_all: Execute queries asynchronously.
  • PgPool: Connection pool for async operations.

Diesel vs SQLx — Comparison

FeatureDieselSQLx
StyleORMSQL-first
Async❌ Mostly sync✅ Fully async
Type SafetyVery highVery high
Learning CurveSteeperEasier for SQL users
Best ForComplex data models, compile-time safetyAsync web APIs, microservices

Which One Should You Use?

  • Choose Diesel if:
    • You want ORM-style code.
    • You prefer compile-time query generation.
    • You’re building complex relational models.
  • Choose SQLx if:
    • You want async database access.
    • You prefer writing SQL directly.
    • You’re building modern REST APIs with Axum/Actix/Rocket.